﻿using System;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using King.Engine.GameObjects;
using System.Collections.Generic;

namespace King.Engine.Core
{

    /// <summary>
    /// Container of game objects that are continuously updated and drawn.
    /// Note that modifying the list of game objects is a lazy process; that is,
    /// calls that modify the list will not be performed until the start of the next update
    /// </summary>
    public abstract class Activity
    {

        #region Fields

        private List<GameObject> _gameObjects;
        private List<GameObject> _newGameObjects;
        private List<GameObject> _removingGameObjects;

        private bool _gameObjectsChanged;

        #endregion


        #region Properties
        
        /// <summary>
        /// Get the readonly list of Game Objects.
        /// </summary>
        public IList<GameObject> GameObjects
        {
            private set;
            get;
        }

        internal bool IsNew;

        #endregion


        #region Constructor

        public Activity()
        {
            _gameObjects = new List<GameObject>();
            _newGameObjects = new List<GameObject>();
            _removingGameObjects = new List<GameObject>();

            IsNew = true;
        }

        #endregion


        #region Public Methods

        public void AddGameObject(GameObject gameObject)
        {
            _newGameObjects.Add(gameObject);
            _gameObjectsChanged = true;
        }

        public void AddGameObjects(IEnumerable<GameObject> gameObjects)
        {
            _newGameObjects.AddRange(gameObjects);
            _gameObjectsChanged = true;
        }

        public void AddGameObjects(params GameObject[] gameObjects)
        {
            AddGameObjects(gameObjects.AsEnumerable());
        }

        public void AddGameObjects(params IEnumerable<GameObject>[] gameObjects)
        {
            AddGameObjects(gameObjects.SelectMany(gs => gs));
        }

        public void RemoveGameObject(GameObject gameObject)
        {
            _removingGameObjects.Add(gameObject);
            _gameObjectsChanged = true;
        }

        public void RemoveGameObjects(IEnumerable<GameObject> gameObjects)
        {
            _removingGameObjects.AddRange(gameObjects);
            _gameObjectsChanged = true;
        }

        public void RemoveGameObjects(params GameObject[] gameObjects)
        {
            RemoveGameObjects(gameObjects.AsEnumerable());
        }

        public void RemoveGameObjects(params IEnumerable<GameObject>[] gameObjects)
        {
            RemoveGameObjects(gameObjects.SelectMany(gs => gs));
        }

        /// <summary>
        /// Remove all game objects that are of type T
        /// </summary>
        public void RemoveObjectsOfType<T>() where T: GameObject
        {
            _removingGameObjects.AddRange(_gameObjects.OfType<T>());
            _gameObjectsChanged = true;
        }

        /// <summary>
        /// Get all game objects that are of type T
        /// </summary>
        public IEnumerable<T> GetObjectsOfType<T>() where T: GameObject
        {
            return _gameObjects.OfType<T>();
        }

        /// <summary>
        /// Get all game objects that are of one of the given types
        /// </summary>
        public IEnumerable<GameObject> GetObjectsOfTypes(IEnumerable<Type> types)
        {
            return _gameObjects.Where(g => types.Contains(g.GetType()));
        }

        /// <summary>
        /// Get all game objects that contain a component of type T
        /// </summary>
        public IEnumerable<GameObject> GetObjectsWithComponent<T>()
        {
            return _gameObjects.Where(g => g.GetComponents<T>().Any());
        }

        /// <summary>
        /// Remove all game objects that are not persistant
        /// </summary>
        public void ClearNonPersistantObjects()
        {
            RemoveGameObjects(GameObjects.Where(g => !g.IsPersistant));
            _gameObjectsChanged = true;
        }

        /// <summary>
        /// Remove all objects
        /// </summary>
        public void ClearAllObjects()
        {
            RemoveGameObjects(GameObjects);
            _gameObjectsChanged = true;
        }

        #endregion


        #region Internal Protected Methods
        
        internal void Update(GameTime gameTime)
        {
            if (_gameObjectsChanged)
            {
                _gameObjects = _gameObjects.Concat(_newGameObjects)
                                           .Except(_removingGameObjects)
                                           .ToList();
                _newGameObjects.Clear();
                _removingGameObjects.Clear();
                GameObjects = _gameObjects.AsReadOnly();

                _gameObjectsChanged = false;
            }

            UpdateBeforeObjects(gameTime);

            foreach (GameObject gameObject in _gameObjects)
            {
                gameObject.Update(gameTime);
            }

            UpdateAfterObjects(gameTime);
        }

        internal void Draw()
        {
            foreach (GameObject gameObject in _gameObjects)
            {
                gameObject.Draw();
            }
        }

        /// <summary>
        /// Called when the activity is first created
        /// </summary>
        internal protected abstract void OnStart();

        /// <summary>
        /// Called when the activity has been set as the current activity
        /// </summary>
        internal protected abstract void OnResume();

        #endregion
        

        #region Protected Methods

        /// <summary>
        /// Perform update logic before objects
        /// </summary>
        protected virtual void UpdateBeforeObjects(GameTime gameTime)
        {
            //do nothing
        }

        /// <summary>
        /// Perform update logic after objects
        /// </summary>
        protected virtual void UpdateAfterObjects(GameTime gameTime)
        {
            //do nothing
        }

        /// <summary>
        /// Perform draw calls that are not handled by game objects
        /// </summary>
        protected virtual void DrawActivity()
        {
            //do nothing
        }

        #endregion

    }
}
